<?php

namespace CMBUBank\UBank;

use CMBUBank\Common\BuilderBase;
use CMBUBank\Common\Encrypt\SM4Encrypt;
use CMBUBank\Http\RequestParameter;
use CMBUBank\UBank\Request\UBankBaseRequest;
use CMBUBank\UBank\Response\UBankBaseResponse;
use Exception;
use FG\ASN1\Exception\ParserException;

class Builder extends BuilderBase
{
    protected $gateway = '';

    protected $userid = '';

    protected $bankPublicKey = '';

    protected $privateKey = '';

    protected $sm4key = '';

    /**
     * 执行请求--构建请求请求参数
     * @param UBankBaseRequest $requestParameter
     * @return UBankBaseResponse
     */
    public function execute(UBankBaseRequest $requestParameter): UBankBaseResponse
    {
        $date = date('YmdHis');
        $uuid = $date . rand(100, 999) . md5(uniqid());

        $requestParameter->setRequestId($uuid);

        $vi = substr($this->userid . '0000000000000000', 0, 16);

        $sm4Encrypt = new SM4Encrypt($this->privateKey, $this->sm4key, $vi, $this->bankPublicKey);

        try {
            //请求参数
            $param = [
                'request' => [
                    'head' => [
                        'funcode' => $requestParameter->getFunCode(),
                        'userid' => $this->userid,
                        'reqid' => $uuid,
                    ],
                    'body' => $requestParameter->generate(),
                ],
                'signature' => [
                    'sigdat' => '__signature_sigdat__',
                    'sigtim' => $date,
                ]
            ];
            //签名
            $sign = $this->signature($sm4Encrypt, $param);

            $param['signature']['sigdat'] = $sign;

            $requestParameter->setPreParameter($param);

            //加密
            $encryptData = $sm4Encrypt->encrypt(json_encode($param, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));

            $postParam = [
                'UID' => $this->userid,
                'FUNCODE' => $requestParameter->getFunCode(),
                'ALG' => 'SM',
                'DATA' => $encryptData
            ];

            $requestParameter->setParameter($postParam);
            //请求
            $response = $this->request($requestParameter);

            $response->setReqCode($requestParameter->getReqCode());

            if ($response->isSuccess()) {
                //解密
                $decrypt = $sm4Encrypt->decrypt($response->getBody());

                $response->setBody($decrypt, true);

                //日记
                $this->request->decryptLogs($uuid, json_encode($response->getBody(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));

                if ($response->isSuccess()) {
                    //验签
                    if (!$this->verifySign($sm4Encrypt, $response->getBody())) {
                        throw new Exception('验签失败');
                    }
                }
            }
            return $response;
        } catch (Exception $exception) {
            $message = $exception->getMessage();
            return $this->exceptionResponse($requestParameter->getResponse(), $message);
        }
    }

    /**
     * 请求参数生成
     * @param RequestParameter $requestParameter
     * @return array
     * @author xis
     */
    public function generate(RequestParameter $requestParameter): array
    {
        return $requestParameter->generate();
    }

    /**
     * 签名
     * @param $sm4Encrypt SM4Encrypt
     * @param $param
     * @return string
     * @throws ParserException
     */
    private function signature(SM4Encrypt $sm4Encrypt, $param): string
    {
        $param = $this->signProSort($param);

        $param = json_encode($param, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        return $sm4Encrypt->signature($param);
    }

    /**
     * 验签
     * @param SM4Encrypt $sm4Encrypt
     * @param $data
     * @return bool
     */
    private function verifySign(SM4Encrypt $sm4Encrypt, $data): bool
    {
        $sign = $data['signature']['sigdat'];

        $data['signature']['sigdat'] = '__signature_sigdat__';

        $data = $this->signProSort($data);

        $data = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        return $sm4Encrypt->verifySign($sign, $data);
    }

    /**
     * 签名字段排序
     * @param $param
     * @return mixed
     */
    private function signProSort(&$param)
    {
        ksort($param);
        foreach ($param as &$value) {
            if (is_array($value)) {
                if ($value) {
                    $this->signProSort($value);
                } else {
                    $value = (object)[];
                }
            }
        }

        return $param;
    }
}
